/**
 * Copyright 2011 Renato de Jesus Manzoni
 * 
 * SED - Software Engineering and Dependability Research Group
 * 
 *     http://www.sed.ic.unicamp.br/
 * 
 * Distributed Systems Laboratory (LSD) in the Institute of Computing (IC) at University of Campinas (UNICAMP)
 * 
 *     http://www.lsd.ic.unicamp.br/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package br.unicamp.ic.lsd.wscaa;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.ClientCallback;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.service.model.BindingFaultInfo;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.perf4j.StopWatch;
import org.perf4j.commonslog.CommonsLogStopWatch;

import br.unicamp.ic.lsd.wscaa.concurrent.ConcurrentExceptionDispatcher;
import br.unicamp.ic.lsd.wscaa.concurrent.ExceptionDispatcher;
import br.unicamp.ic.lsd.wscaa.exception.WSNotFoundException;
import br.unicamp.ic.lsd.wscaa.interceptors.WSFaultSoapInterceptor;

/**
 * @author Renato de Jesus Manzoni <rjmanzoni@gmail.com>
 * 
 */
public class WebService {
    private final Client client;
    private List<String> exceptions;
    private List<String> operations;
//    private static final int THREAD_POOL_SIZE = 10;
    private WSFaultSoapInterceptor interceptor;

    private static Log logger = LogFactory.getLog(WebService.class);
//
    public WebService(Client client) {
	this.client = client;

	//prende o sistema quando executada na main (Estudo de caso 1)
	this.client.setExecutor(Executors.newCachedThreadPool());

	// ok
	interceptor = new WSFaultSoapInterceptor();

	// interceptor.setHandler(new
	// ConcurrentExceptionSync(TIME_WINDOW_RESOLUTION,
	// TimeUnit.MILLISECONDS));
//	this.client.getInInterceptors().
	this.client.getInFaultInterceptors().add(interceptor);
	
	createExceptionList();

	createOperationsSupported();
    }

//    public WebService(JaxWsDynamicClientFactory jcf){
//	jcf.createClient("");
//    }
    
    public WebService(Client client, ExceptionDispatcher dispatcher) {
	this(client);

	interceptor.setHandler(dispatcher);

    }
    
    public WebService(String wsdlURL, WSCAManager manager) {
	this(JaxWsDynamicClientFactory.newInstance().createClient(wsdlURL,ClassLoader.getSystemClassLoader()));
	
	ConcurrentExceptionDispatcher dispatcher = new ConcurrentExceptionDispatcher(manager);
	interceptor.setHandler(dispatcher);
    }

//    public WebService(String wsdlURL, WSCAManager manager, Bus b, QName qn) {
//	this(JaxWsDynamicClientFactory.newInstance().createClient(wsdlURL,ClassLoader.getSystemClassLoader()));
//	
//	ConcurrentExceptionDispatcher dispatcher = new ConcurrentExceptionDispatcher(manager);
//	interceptor.setHandler(dispatcher);
//    }
    
    public void invokeAsyncMethod(ClientCallback callback, String method,
	    Object... params) throws Exception {

	if (logger.isDebugEnabled())
	    logger.debug("Invoking the service " + method);
	
	final StopWatch stopWatch = new CommonsLogStopWatch();
	// TODO pre-validation...
	client.invoke(callback, method, params);
	// TODO pos-validation...
	stopWatch.stop("WebService.invokeAsyncMethod.sucess");

    }

    public void invokeAsyncMethod(ClientCallback callback,
	    BindingOperationInfo bo, Object[] params) throws Exception {

	client.invoke(callback, bo, params);

    }

    // TODO mudar esse metodo pra outra classe?
    public String extractExceptionName(Class<? extends Exception> classEx)
	    throws WSNotFoundException {
	String classExException = classEx.toString();

	for (Iterator<String> iterator = exceptions.iterator(); iterator
		.hasNext();) {

	    String ex = iterator.next();

	    if (classExException.contains(ex))
		return ex;

	}

	throw new WSNotFoundException(
		"Exception not found in the dinamic invokation from ws: "
			+ client.getConduit().getTarget().getAddress()
				.getValue());
    }

    private void createExceptionList() {
	exceptions = new ArrayList<String>();

	Collection<BindingOperationInfo> operations = client.getEndpoint()
		.getBinding().getBindingInfo().getOperations();

	for (Iterator<BindingOperationInfo> iteratorBOI = operations.iterator(); iteratorBOI
		.hasNext();) {
	    BindingOperationInfo bindingOperationInfo = iteratorBOI.next();

	    Collection<BindingFaultInfo> exceptionsOp = bindingOperationInfo
		    .getFaults();

	    for (Iterator<BindingFaultInfo> iterator2BFI = exceptionsOp
		    .iterator(); iterator2BFI.hasNext();) {

		BindingFaultInfo bindingFaultInfo = iterator2BFI.next();
		exceptions.add(bindingFaultInfo.getFaultInfo().getFaultName()
			.getLocalPart());

	    }

	}
    }

    private void createOperationsSupported() {

	operations = new ArrayList<String>();

	Collection<BindingOperationInfo> collection = client.getEndpoint()
		.getBinding().getBindingInfo().getOperations();

	for (Iterator<BindingOperationInfo> iterator = collection.iterator(); iterator
		.hasNext();) {
	    BindingOperationInfo boi = iterator.next();
	    operations.add(boi.getName().getLocalPart());
	}
    }

    public List<String> getExceptions() {
	return exceptions;
    }

    public void setExceptions(List<String> exceptions) {
	this.exceptions = exceptions;
    }

    public String[] getOperations() {
	return operations.toArray(new String[operations.size()]);
    }

}